﻿namespace Microsoft.VisualStudio.TextTemplating.CommandLine
{
    using System;
    using System.Globalization;
    using System.Reflection;

    internal class CommandFactory
    {
        private OptionParser argumentParser;
        private CommandSpecificationCollection commands;

        public CommandFactory(Assembly[] commandAssemblies, OptionParser parser)
        {
            this.argumentParser = parser;
            this.LoadCommandSpecification(commandAssemblies);
        }

        public CommandFactory(Assembly commandAssembly, OptionParser parser) : this(new Assembly[] { commandAssembly }, parser)
        {
        }

        public Command Create(string[] arguments)
        {
            Command command = null;
            string str = this.argumentParser.ParseCommandName(arguments);
            CommandSpecification specification = this.Commands[str];
            if (specification == null)
            {
                throw new UsageException(string.Format(CultureInfo.CurrentCulture, "Unknown command '{0}'", new object[] { str }));
            }
            try
            {
                command = Activator.CreateInstance(specification.CommandType) as Command;
                command.Specification = specification;
                CommandOptionCollection options = this.argumentParser.Parse(arguments, specification.OptionSpecifications);                
                foreach (OptionSpecification specification2 in specification.OptionSpecifications)
                {
                    if ((specification2.DefaultValue != null) && !options.Contains(specification2.OptionName))
                    {
                        CommandOption option = new CommandOption(specification2.OptionName);
                        option.Add(specification2.DefaultValue);
                        options.Add(option);
                    }
                }
                if (!command.Specification.AllowNoNameOptions && (options[""] != null))
                {
                    throw new UsageException(string.Format(CultureInfo.CurrentCulture, "Invalid option '{0}'", new object[] { options[""].Value }));
                }
                foreach (OptionSpecification specification3 in specification.OptionSpecifications)
                {
                    if ((specification3.RelatedProperty != null) && options.Contains(specification3.OptionName))
                    {
                        this.argumentParser.SetOptionProperty(command, specification3, options[specification3.OptionName]);
                        options.Remove(specification3.OptionName);
                    }
                }
                command.Load(options);
                if (command.Output == null)
                {
                    command.Output = Console.Out;
                }
                if (command.Error == null)
                {
                    command.Error = Console.Error;
                }
            }
            catch (CommandException exception)
            {
                if (exception.Command == null)
                {
                    exception.Command = command;
                }
                throw;
            }
            return command;
        }

        private void LoadCommandSpecification(Assembly[] commandAssemblies)
        {
            this.commands = new CommandSpecificationCollection();
            foreach (Assembly assembly in commandAssemblies)
            {
                foreach (Type type in assembly.GetTypes())
                {
                    if (type.GetCustomAttributes(typeof(CommandAttribute), false).Length > 0)
                    {
                        CommandSpecification specification = new CommandSpecification(type);
                        this.Commands.Add(specification);
                    }
                }
            }
        }

        public CommandSpecificationCollection Commands
        {
            get
            {
                return this.commands;
            }
        }
    }
}

